/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ExtensionContext, ExtensionManager, PluginExtension } from '../extension';
import { Logger, TsAstUtil } from '../utils';
import { TaskStage } from './TaskStage';

/**
 * Task manager for plugin execution
 *
 * This class manages the execution of plugin tasks across different stages
 * of the build process.
 * It coordinates execution of extension hooks and
 * analyzer registration.
 */
export class TaskManager {
  private extensionManager: ExtensionManager;
  private readonly moduleExtensions: PluginExtension[];
  public context: ExtensionContext;

  /**
   * Creates a new TaskManager
   *
   * @param {ExtensionContext} context - The shared extension context
   * @param {PluginExtension[]} [moduleExtensions] - Optional array of module extensions
   */
  constructor(
    context: ExtensionContext,
    moduleExtensions?: PluginExtension[]
  ) {
    this.extensionManager = ExtensionManager.getInstance();
    this.moduleExtensions = moduleExtensions || [];
    this.context = context;
  }

  /**
   * Execute a specific stage of the build process
   *
   * This method dispatches execution to the appropriate handler for each stage.
   * For annotation analysis, it uses a specialized execution flow.
   * For all other stages, it invokes the corresponding method on each extension.
   *
   * @param {TaskStage} stage - The stage to execute
   */
  executeStage(stage: TaskStage): void {
    switch (stage) {
      case TaskStage.AFTER_ANNOTATION_ANALYSIS:
        this.executeAnnotationAnalysis();
        break;
      default:
        this.getExtensions().forEach((extension) => {
          const methodName = stage.toString();
          (extension as any)[methodName]?.(this.context);
        });
        break;
    }
  }

  /**
   * Execute the annotation analysis stage
   *
   * This method:
   * 1. Collects analyzers from all extensions
   * 2. Processes all source files with the collected analyzers
   * 3. Validates template data consistency
   * 4. Cleans up by clearing the analyzer registry
   *
   * @private
   */
  private executeAnnotationAnalysis(): void {
    const extensions = this.getExtensions();

    this.context.scanFiles.forEach((file: string) => {
      // Set current file path and clear currentView for the new file
      (this.context as any).setCurrentFilePath(file);

      const sourceFile = TsAstUtil.getSourceFile(file);
      // Iterate through extensions and call their afterAnnotationAnalysis method with correct this context
      extensions.forEach((extension) => {
        if (extension.afterAnnotationAnalysis) {
          extension.afterAnnotationAnalysis(sourceFile, file, this.context);
        }
      });
    });

    // Clear current file path after processing all files
    (this.context as any).setCurrentFilePath('');

    // Validate template data consistency
    this.validateTemplateData();
  }

  /**
   * Get all registered extensions, including module-specific ones
   *
   * @returns {PluginExtension[]} Array of all extensions
   * @private
   */
  private getExtensions(): PluginExtension[] {
    const globalExtensions = this.extensionManager.getExtensions();
    const allExtensions = globalExtensions.concat(this.moduleExtensions);

    // Ensure HMRouter extensions execute first by sorting
    return allExtensions.sort((a, b) => {
      const aIsHMRouter = a.name?.includes('HMRouter') || false;
      const bIsHMRouter = b.name?.includes('HMRouter') || false;

      if (aIsHMRouter && !bIsHMRouter) {
        return -1;
      } // a comes first
      if (!aIsHMRouter && bIsHMRouter) {
        return 1;
      } // b comes first
      return 0; // maintain original order
    });
  }

  /**
   * Validate template data against analysis results
   *
   * This ensures that all template data references are valid components.
   * If there's template data for a component that doesn't exist in the
   * analysis results, a warning is logged.
   *
   * @private
   */
  private validateTemplateData(): void {
    // Build a set of all known component names from analysis results
    const componentNames = new Set<string>();
    this.context.getAnalyzeResults().forEach((result) => {
      if (result.name) {
        componentNames.add(result.name);
      }
    });

    // Get all component names from template data
    const templateComponentNames = new Set(this.context.getTemplateDataMap().keys());

    // Check for components with template data but no analysis results
    templateComponentNames.forEach((name) => {
      if (!componentNames.has(name)) {
        Logger.warn(
          this.context.node.getNodeName(),
          `Component ${name} has template data but was not found in analyze results.`
        );
      }
    });
  }
}
